package org.dei.perla.sys.query.llqruntime.datacollection;

import org.dei.perla.parser.clauses.*;
import org.dei.perla.parser.expressions.Node;
import org.dei.perla.sys.device.fpc.FunctionalityProxyComponent;
import org.dei.perla.sys.query.llqruntime.datastructures.*;
import org.dei.perla.utils.pipe.*;
import org.dei.perla.utils.waiter.*;

/**
 * Questa classe rappresenta l'oggetto incaricato del recupero dei dati campionati da parte
 * dell'<tt>FPC</tt>.
 */
public class DataCollector extends Thread{
		

	/**
	 * Tutte le informazioni riguardo l'attivita' di data collecting.
	 */
	private LLDataCollectionInfo pInfo;
	private FunctionalityProxyComponent pFPC;
	private Buffer pBuffer;
	private SamplingData pSamplingData;
	private Waiter<Pipe<Record>> pRecordWaiter;
	private Node pWhereNode;
	
	
	/**
	 * Flag per marcare la situazione "primo record ricevuto"
	 */
	private boolean isFirstRecord = true;
	
	/**
	 * Flag per marcare che e' gia' attivo un gestore di REFRESH
	 */
	private RefreshHandler tRefreshHandler = null;
	
	
	/**
	 * Oggetto che si occupa di gestire la clausola REFRESH
	 */
	private class RefreshHandler implements Runnable {
		
		long pTime;
		
		public RefreshHandler(long parTime) {
			this.pTime = parTime;
		}
				
		public void run() {
			try {
				sleep(pTime);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			pFPC.setSamplingTime(readFrequencyFromTable(pFPC.forceSampling().get(0)),pSamplingData.getBehaviour());
		}
	}
	
	/**
	 * Costruisce un nuovo oggetto del tipo DataCollector
	 * @param parInfo le informazioni di cui ha bisogno il DataCollector
	 */
	public DataCollector(LLDataCollectionInfo parInfo) {
		
		this.pInfo = parInfo;
		this.pBuffer = parInfo.getLocalBuffer();
		this.pFPC = parInfo.getAssociatedFPC();
		this.pSamplingData = parInfo.getSamplingData();
		this.pWhereNode = parInfo.getWhereCondition();
		pRecordWaiter = new Waiter<Pipe<Record>>();
		pRecordWaiter.start();
		pRecordWaiter.addWaitable(pFPC.getNewOutputPipe().getWaitable());
		//pRecordWaiter.addWaitable(((FPCmockup) pFPC).getPipe((LLStatement) parInfo.getRepresentedStatement()).getWaitable());
	}
	
	/**
	 * @return Le informazioni di basso livello associate a questo DataCollector
	 */
	public LLDataCollectionInfo getLLDataCollectionInfo() {
		return this.pInfo;
	}
	
	/**
	 * Mette in esecuzione il DataCollector
	 */
	public void run() { 
		
		Record tRecord;
		System.out.println("*** DATA COLLECTOR starts ***");
		Waitable<Pipe<Record>> tTmpWaitable;
		Pipe<Record> tTmpPipe;
		
		//....ciclo principale di raccolta dati
		
		do {
			tTmpWaitable = pRecordWaiter.waitNext();
			tTmpPipe = tTmpWaitable.getParentComponent();
			tRecord = tTmpPipe.dequeue();				
							
			if(tRecord == null) {
				System.out.println("PIPE CHIUSA");
				tTmpPipe.stop();
				pRecordWaiter.stop();
				tRecord = null;
				break;
			}
			
			System.out.println("Record letto da "+pFPC.getName());
			
			if (performWhere(tRecord)) {
				//...i record che si "salvano" dopo la performWhere() vengono inseriti nell'LocBuf				
				pBuffer.addRecord(tRecord);
				System.out.println("Record aggiunto dal DC collegato a "+pFPC.getName());
				System.out.println("["+pBuffer.getBuffer().size()+"]"+"<---Il record ora nel buffer contiene il valore: \n" + tRecord);
			}	
			
			else {
				System.out.println("*** Record SCARTATO dalla WHERE***");
			}

			
			//...valuto se è il caso di cambiare la frequenza di campionamento....
			evaluateFrequency(tRecord, isFirstRecord);
			isFirstRecord = false;
			
			//TODO inserire qui la gestione della terminate (basta un "break")
			
			
		} while (true);
		
		System.out.println("*** DATA COLLECTOR ha terminato ***");
		//TODO bisogna inserire qui tutta le gestione della terminazione della query
	}
	
	/**
	 * Metodo helper: data una tabella e un record aggiorna la frequenza di campionamento sull'FPC
	 * @param parTable la tabella
	 * @param parRecord il record
	 * @return TRUE se ok, FALSE altrimenti
	 */
	private long readFrequencyFromTable(Record parRecord) {
		
		long foundFrequency = 0;
		
		for (FrequencyRecord tFreqRec : pSamplingData.getSamplingTableContent()) {
			
			if (ExpressionEvaluator.evaluate(parRecord,tFreqRec.getFirstOperand())) {
				foundFrequency = (long) tFreqRec.getFrequency().getValue();
				break;
				//bValue = ((FPCmockup) pFPC).setMockupSamplingTime((long)tFreqRec.getFrequency().getValue()*1000,null,(LLStatement) pInfo.getRepresentedStatement());
				//bValue = (pFPC.setSamplingTime((long)tFreqRec.getFrequency().getValue()*1000, parTable.getBehaviour(),pInfo.getRepresentedStatement()));
				//if (bValue == true) break;
			}
			
		}
		return foundFrequency;
	}
	/**
	 * Metodo helper per la valutazione della clausola Where
	 * @param parRecord il record su cui controllare la clausola WHERE
	 * @return TRUE se il record è accettabile, FALSE se va' scartato
	 */
	private boolean performWhere(Record parRecord) {
		
		return ExpressionEvaluator.evaluate(parRecord,pWhereNode);
	}
	
	/**
	 * Metodo interno che si occupa della parte riguardante le tempistiche e la casistiche di sampling
	 * @param tRecord
	 * @param isFirstTime indica se e' la prima volta che il DC setta la frequenza. Infatti il primo record viene usato
	 * per settare la frequenza. Al giro successivo e' compito della REFRESH occuparsene.
	 * @return TRUE se tutto ok, FALSE otherwise.
	 */
	private boolean evaluateFrequency(Record tRecord, boolean isFirstTime) {
		
		/*E' il primo record che e' stato ricevuto?*/
		
		if (isFirstTime) {
			//...allora setto la prima frequenza di campionamento..
			this.pFPC.setSamplingTime(readFrequencyFromTable(tRecord),pSamplingData.getBehaviour());	
		}
		else {
			//...a questo punto almeno un settaggio della FC (frequenza di campionamento) è stato fatto.
			//..devo vedere se è il caso di fare REFRESH e che tipo di REFRESH:
			
			if (pSamplingData.getRefreshClause() instanceof RefreshNever) {				
				//..l'utente ha imposto nessun REFRESH... la prima frequenza impostata rimane per sempre.
			}
			else if (pSamplingData.getRefreshClause() instanceof RefreshEventBased) {
				
				//Qui e' tutto un open point. Dal mio punto di vista io farei cosi':
				//1. aggiungerei interfaccia all'FPC per un'altra pipe di Eventi
				//2. aspetto gli eventi che salgono e me li segno
				//3. se tra quelli che ho segnato c'è quello che mi interessa...aggiorno...
								
			}
			else if (pSamplingData.getRefreshClause() instanceof RefreshTimeBased) {
				
				if (tRefreshHandler == null) {
					tRefreshHandler = new RefreshHandler((long) ((RefreshTimeBased) pSamplingData.getRefreshClause()).getInterval().getValue());
					tRefreshHandler.run();
				}
				
			}
		}
		return true;
	}
} 